#include "testbedGL.hpp"
#include <boost/thread.hpp>
#include "collada/collada.h"
#include "bt_interface.hpp"
#include "gl_interface.hpp"
#include "utils.hpp"

using namespace  Vectormath::Aos;
using namespace TestbedGL;
Camera freecam;
//This should be accessible in process_events for controls handling
boost::shared_ptr<BT_Interface::Vehicle>vh;

//Simple shadow map rendering
struct RenderShadowMap:public TestbedGL::Pass
{
    UniformShadowCamera cam;
    TestbedGL::Texture color,depth;
    RenderShadowMap()
    {
        textures["depth"].build("GL_DEPTH_COMPONENT",1024,1024,0,false);
        textures["color"].build("GL_RGB",1024,1024,0,false);
        textures["some"].load_image("textures/terrain.tga");
        shader.load("shaders/zbuffer.vs","shaders/zbuffer.fs");
        shader.add_variable("object");
        shader.add_variable("mvp");
        //matrices["mvp"] = Matrix4GL::identity();
        rt.init(1024,1024,textures["color"],textures["depth"]);
    }
    void Render()
    {

        glEnableClientState(GL_VERTEX_ARRAY);
        glEnable(GL_DEPTH_TEST);

        rt.bind();
        shader.bind();

        glClearColor(0.5,0.7,0.6,0.0);
        glClear( GL_COLOR_BUFFER_BIT| GL_DEPTH_BUFFER_BIT );
        shader["mvp"].pass_matrix(cam.mvp);
		for(std::vector<boost::shared_ptr<TestbedGL::SceneNode> >::iterator n = nodes.begin(); n != nodes.end(); n++)
        {
            TestbedGL::SceneNode *node = n->get();
            for(std::vector< boost::shared_ptr<TestbedGL::Geometry> >::iterator g = node->geometry_ids.begin(); g!= node->geometry_ids.end() ; g++)
            {
                (*g)->bind();
                shader["object"].pass_matrix(node->matrix);
                glDrawElements( GL_TRIANGLES, (*g)->faces.size, GL_UNSIGNED_INT, (*g)->faces.array.get());
            }
			for(std::vector<boost::shared_ptr<TestbedGL::SceneNode> >::iterator st = node->children.begin(); st != node->children.end(); st++)
                for(std::vector< boost::shared_ptr<TestbedGL::Geometry> >::iterator g = (st->get())->geometry_ids.begin(); g!= (st->get())->geometry_ids.end() ; g++)
                {
                    (*g)->bind();
                    shader["object"].pass_matrix((st->get())->matrix);
                    glDrawElements( GL_TRIANGLES, (*g)->faces.size, GL_UNSIGNED_INT, (*g)->faces.array.get());
                }
        }

        rt.unbind();



    }
};


//Rendering the car. We don't need any self-shadowing so shader is way simpler than for terrain.
struct RenderCar:public TestbedGL::Pass
{
    RenderCar()
    {
        shader.load("shaders/specular.vs","shaders/specular.fs");
        shader.add_variable("mvp");
        shader.add_variable("object");
        shader.add_variable("texture");
        textures["tt_audi"].load_image("textures/AudiTT.tga");
        matrices["mvp"] = Matrix4GL::identity();


    }

    void Render()
    {
        glEnableClientState(GL_VERTEX_ARRAY);
        glEnableClientState(GL_NORMAL_ARRAY);
        glEnableClientState(GL_TEXTURE_COORD_ARRAY);
        glEnable(GL_COLOR_MATERIAL);
        shader.bind();
        shader["mvp"].pass_matrix(matrices["mvp"]);
        shader["texture"].pass_sampler(textures["tt_audi"],0);

		for(std::vector<boost::shared_ptr<TestbedGL::SceneNode> >::iterator n = nodes.begin(); n != nodes.end(); n++)
        {
            TestbedGL::SceneNode *node = n->get();
            for(std::vector< boost::shared_ptr<TestbedGL::Geometry> >::iterator g = node->geometry_ids.begin(); g!= node->geometry_ids.end() ; g++)
            {
                (*g)->bind();
                shader["object"].pass_matrix(node->matrix);
                glDrawElements( GL_TRIANGLES, (*g)->faces.size, GL_UNSIGNED_INT, (*g)->faces.array.get());
            }
            for(std::vector<boost::shared_ptr<TestbedGL::SceneNode> >::iterator st = node->children.begin(); st != node->children.end(); st++)
                for(std::vector< boost::shared_ptr<TestbedGL::Geometry> >::iterator g = (st->get())->geometry_ids.begin(); g!= (st->get())->geometry_ids.end() ; g++)
                {
                    (*g)->bind();
                    shader["object"].pass_matrix((st->get())->matrix);
                    glDrawElements( GL_TRIANGLES, (*g)->faces.size, GL_UNSIGNED_INT, (*g)->faces.array.get());
                }
        }


    }
};

//Rendering terrain with shadowing
struct RenderTerrain:public TestbedGL::Pass
{
    TestbedGL::Matrix4GL tex_mat;
    RenderTerrain()
    {
        shader.load("shaders/shader.vs","shaders/shader.fs");
        shader.add_variable("mvp");
        shader.add_variable("object");
        shader.add_variable("texture");
        shader.add_variable("texture1");
        shader.add_variable("ShadowMap");
        shader.add_variable("texMatrix");

        textures["terrain"].load_image("textures/terrain.tga");
        textures["detail"].load_image("textures/terrain_detail.tga");
        matrices["mvp"] = Matrix4GL::identity();
        matrices["texMatrix"] = Matrix4GL::identity();
    }

    void Render()
    {
        glEnable(GL_CULL_FACE);
        glCullFace(GL_BACK);
        glEnableClientState(GL_VERTEX_ARRAY);
        glEnableClientState(GL_TEXTURE_COORD_ARRAY);

        shader.bind();
        shader["mvp"].pass_matrix(matrices["mvp"]);

        shader["texture"].pass_sampler(textures["terrain"],0);
        shader["texture1"].pass_sampler(textures["detail"],1);
        shader["ShadowMap"].pass_sampler(textures["depth"],2);

        shader["texMatrix"].pass_matrix(matrices["texMatrix"]);

        for(std::vector<boost::shared_ptr<TestbedGL::SceneNode> >::iterator n = nodes.begin(); n != nodes.end(); n++)
        {
            TestbedGL::SceneNode *node = n->get();
            for(std::vector< boost::shared_ptr<TestbedGL::Geometry> >::iterator g = node->geometry_ids.begin(); g!= node->geometry_ids.end() ; g++)
            {
                (*g)->bind();
                shader["object"].pass_matrix(node->matrix);
                glDrawElements( GL_TRIANGLES, (*g)->faces.size, GL_UNSIGNED_INT, (*g)->faces.array.get());
            }
            for(std::vector<boost::shared_ptr<TestbedGL::SceneNode> >::iterator st = node->children.begin(); st != node->children.end(); st++)
                for(std::vector< boost::shared_ptr<TestbedGL::Geometry> >::iterator g = (st->get())->geometry_ids.begin(); g!= (st->get())->geometry_ids.end() ; g++)
                {
                    (*g)->bind();
                    shader["object"].pass_matrix((st->get())->matrix);
                    glDrawElements( GL_TRIANGLES, (*g)->faces.size, GL_UNSIGNED_INT, (*g)->faces.array.get());
                }
        }

    }
};


static void quit_program( int code )
{
    SDL_Quit( );
    exit( code );
}

static void process_events( void )
{

    SDL_Event event;
	//We need to move steering to zero all the time or controls will be a bit incovinient
	static float brakes,engine,steering;
    steering -=steering*0.1f;
    engine -=engine*0.1f;
    static float x = 0;
    static float y = 0;
    while ( SDL_PollEvent( &event ) )
    {

        switch ( event.type )
        {
        case SDL_MOUSEMOTION:
            x += (float)event.motion.xrel/1000.0;
            y += (float)event.motion.yrel/1000.0;
            freecam.RotateByMouse(x,y);

            break;

        case SDL_MOUSEBUTTONDOWN:
            break;

        case SDL_MOUSEBUTTONUP:
            break;

        case SDL_KEYDOWN:
            if (event.key.keysym.sym == SDLK_ESCAPE)
            {
                quit_program(0);
            }

            if (event.key.keysym.sym == SDLK_UP)
            {
                freecam.Move(freecam.GetDir()*3.0);
            }
            if (event.key.keysym.sym == SDLK_DOWN)
            {
                freecam.Move(freecam.GetDir()*(-3.0));
            }
            if (event.key.keysym.sym == SDLK_RIGHT)
            {

            }
            if (event.key.keysym.sym == SDLK_LEFT)
            {

            }


            if (event.key.keysym.sym == SDLK_w)
            {
                engine -=2.2f;
            }
            if (event.key.keysym.sym == SDLK_s)
            {
                engine +=2.2f;
            }

            if (event.key.keysym.sym == SDLK_a)
            {
                steering +=0.05f;
            }

            if (event.key.keysym.sym == SDLK_d)
            {
                steering -=0.05f;
            }

            if (event.key.keysym.sym == SDLK_SPACE)
            {
                engine = 0.0f;
                brakes = 0.6f;
            }
            else
            {
                brakes = 0.0f;
            }

            if(engine > 225.0f) engine = 225.0f;
            if(engine < -55.0f) engine = -55.0f;

            if(steering > 0.9f) steering = 0.9f;
            if(steering < -0.9f) steering = -0.9f;




            break;
        case SDL_QUIT:
            /* Handle quit requests (like Ctrl-c). */
            quit_program( 0 );
            break;
        }

    }
	//We need to update it all the time.
    vh->update_params(steering,engine,brakes);
}



#ifdef __GNUC__
#ifdef WIN32
#include <windows.h>
int WINAPI WinMain(
    HINSTANCE hInstance,
    HINSTANCE hPrevInstance,
    LPSTR lpCmdLine,
    int nCmdShow)
#else
int main(int argc, const char **params)
#endif
#else
int wmain(int argc, const char **params)
#endif
{
    //We don't need this objects outside the main function
	GL_Interface::GraphicsLibrary lib;
	BT_Interface::World wld;
	boost::shared_ptr<RenderTerrain>rter;
	boost::shared_ptr<RenderCar> rcar;
	boost::shared_ptr<RenderShadowMap> smap;
	boost::shared_ptr<TestbedGL::SceneNode> node;
	boost::shared_ptr<TestbedGL::SceneNode> gplane,earth;
	boost::shared_ptr<TestbedGL::Window>window;
	Camera gcamera;

	window = boost::shared_ptr<TestbedGL::Window>(new TestbedGL::Window(1024,768,24));

    lib.load_collada("models/car.dae");

    lib.load_collada("models/stones.dae");

    glEnable(GL_DEPTH_TEST);
	node = boost::shared_ptr<TestbedGL::SceneNode> (new TestbedGL::SceneNode(*(lib.get_node("models/car.dae"))));
    earth = boost::shared_ptr<TestbedGL::SceneNode> (new TestbedGL::SceneNode(*(lib.get_node("models/stones.dae"))));


    for(int i =1 ; i < 5; i++)
        node->children[i]->base_matrix.set_translation(0.0f,0.0f,0.0f);

	//Loading heightmap
	Heightmap map;
    map.load("textures/map.tga");


	gplane = lib.make_grid(map,"plane",256,256,2.0f,2.0f,0.01f,0.01f);
    gplane->matrix = Matrix4::identity();
    gplane->matrix.setTranslation(Vector3(0.0f,0.0f,0.0f));


    BT_Interface::Object plane;
    plane.make_heightmap(map,btVector3(0.0f,0.0f,0.0f));
    wld.add_object(plane);

	vh = boost::shared_ptr<BT_Interface::Vehicle>(new BT_Interface::Vehicle());
    vh->add_vehicle_to_world(wld);
    vh->set_body(10.0f,btVector3(1.5f,0.5f,4.0f),btVector3(200.0f,29.0f,80.0f),btQuaternion(0.0f,0.0f,0.0f,1.0f));
    vh->set_wheel_common_parameters();
    vh->add_wheel("none",btVector3(1.0,-0.1 ,-2.2),0.5,0.2,true);
    vh->add_wheel("none",btVector3(-1.0,-0.1, -2.2),0.5,0.2,true);
    vh->add_wheel("none",btVector3(1.0,-0.1,1.3),0.5,0.2,false);
    vh->add_wheel("none",btVector3(-1.0,-0.1,1.3),0.5,0.2,false);

	rcar = boost::shared_ptr<RenderCar>(new RenderCar);
	rter = boost::shared_ptr<RenderTerrain>(new RenderTerrain);
	smap = boost::shared_ptr<RenderShadowMap>(new RenderShadowMap);

	//Depth texture from shadow map pass should be accessible in terrain pass.
	rter->textures["depth"] = smap->textures["depth"];

    smap->nodes.push_back(node);
    rcar->nodes.push_back(node);
    rter->nodes.push_back(gplane);
    rter->nodes.push_back(earth);



	float scale = 0.5f;
	freecam.modelview.set_translation(200.0f,29.0f,80.0f);
    gcamera.Frustum(-1.333f*scale,1.333f*scale,-1.0f*scale,1.0f*scale,1.0f,10000.0f);
    freecam.Frustum(-1.333f*scale,1.333f*scale,-1.0f*scale,1.0f*scale,1.0f,10000.0f);


    node->build_dict();

    float matrix[16];
    while (1)
    {
        wld.do_step(0.05);
        //We should sync car physics model and car graphics model every frame
        btTransform trans;
        vh->body.body->getMotionState()->getWorldTransform(trans);
        trans.getOpenGLMatrix(matrix);
        vh->update_wheels();
        node->base_matrix = Matrix4::identity();
        (*node)["Shassi01"]->matrix.set_matrix(matrix);
        (*node)["Shassi01"]->matrix *= (*node)["Shassi01"]->base_matrix;
        for(int i = 0; i < 4; i++)
        {
            std::string name = "Wheel";
            name.append(1,'1'+i);
            (*node)[name]->matrix = vh->wheel_matrices[i];
            (*node)[name]->matrix *= (*node)[name]->base_matrix;
        }

		//Calculating the third person camera for car
        Point3 car =  Point3((*node)["Wheel1"]->matrix.getTranslation());
        Vector3 vec = Matrix4GL(inverse((*node)["Wheel1"]->matrix)).get_dir();
        Point3 pos = car -  cross(vec,Vector3(0.0f,1.0f,0.0f))*20.0f + Vector3(0.0f,5.0f,0.0f);
        Vector3 lookAt =  car - pos;

        //Setup the light camera to follow the car.
        smap->cam.SetupLightMatrix(Point3(car + Vector3(0.0f,20.0f,20.0f)),Point3(car));
        smap->cam.SetupTextureMatrix();

        rter->matrices["texMatrix"] = smap->cam.texMatrix;
        gcamera.pos = pos;
        gcamera.lookAt = lookAt;
        gcamera.update();
        freecam.update();

        rter->matrices["mvp"] = freecam.mvp;
        rcar->matrices["mvp"] = freecam.mvp;


		//Rendering the stuff
	    smap->Render();
	    glClearColor(0.5,0.7,0.6,0.0);
		glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	    rter->Render();
	    rcar->Render();

        glFinish();
        SDL_GL_SwapBuffers();

        process_events( );

    }
    return 0;
}
